/*
 * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.oa.poa;

import java.util.Set;

import org.omg.CORBA.SystemException;

import org.omg.PortableServer.ServantActivator;
import org.omg.PortableServer.Servant;
import org.omg.PortableServer.ServantManager;
import org.omg.PortableServer.ForwardRequest;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.ServantNotActive;
import org.omg.PortableServer.POAPackage.ObjectAlreadyActive;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive;
import org.omg.PortableServer.POAPackage.NoServant;

import com.sun.corba.se.impl.orbutil.concurrent.SyncUtil;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.orbutil.ORBConstants;

import com.sun.corba.se.impl.oa.NullServantImpl;

import com.sun.corba.se.impl.javax.rmi.CORBA.Util;

import com.sun.corba.se.spi.oa.OAInvocationInfo;
import com.sun.corba.se.spi.oa.NullServant;

/**
 * Implementation of POARequesHandler that provides policy specific
 * operations on the POA.
 */
public class POAPolicyMediatorImpl_R_USM extends POAPolicyMediatorBase_R {

  protected ServantActivator activator;

  POAPolicyMediatorImpl_R_USM(Policies policies, POAImpl poa) {
    // assert policies.retainServants()
    super(policies, poa);
    activator = null;

    if (!policies.useServantManager()) {
      throw poa.invocationWrapper().policyMediatorBadPolicyInFactory();
    }
  }

  /* This handles a rather subtle bug (4939892).  The problem is that
   * enter will wait on the entry if it is being etherealized.  When the
   * deferred state transition completes, the entry is no longer in the
   * AOM, and so we need to get a new entry, otherwise activator.incarnate
   * will be called twice, once for the old entry, and again when a new
   * entry is created.  This fix also required extending the FSM StateEngine
   * to allow actions to throw exceptions, and adding a new state in the
   * AOMEntry FSM to detect this condition.
   */
  private AOMEntry enterEntry(ActiveObjectMap.Key key) {
    AOMEntry result = null;
    boolean failed;
    do {
      failed = false;
      result = activeObjectMap.get(key);

      try {
        result.enter();
      } catch (Exception exc) {
        failed = true;
      }
    } while (failed);

    return result;
  }

  protected java.lang.Object internalGetServant(byte[] id,
      String operation) throws ForwardRequest {
    if (poa.getDebug()) {
      ORBUtility.dprint(this,
          "Calling POAPolicyMediatorImpl_R_USM.internalGetServant " +
              "for poa " + poa + " operation=" + operation);
    }

    try {
      ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
      AOMEntry entry = enterEntry(key);
      java.lang.Object servant = activeObjectMap.getServant(entry);
      if (servant != null) {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: servant already activated");
        }

        return servant;
      }

      if (activator == null) {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: no servant activator in POA");
        }

        entry.incarnateFailure();
        throw poa.invocationWrapper().poaNoServantManager();
      }

      // Drop the POA lock during the incarnate call and
      // re-acquire it afterwards.  The entry state machine
      // prevents more than one thread from executing the
      // incarnate method at a time within the same POA.
      try {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: upcall to incarnate");
        }

        poa.unlock();

        servant = activator.incarnate(id, poa);

        if (servant == null) {
          servant = new NullServantImpl(
              poa.omgInvocationWrapper().nullServantReturned());
        }
      } catch (ForwardRequest freq) {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: incarnate threw ForwardRequest");
        }

        throw freq;
      } catch (SystemException exc) {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: incarnate threw SystemException " + exc);
        }

        throw exc;
      } catch (Throwable exc) {
        if (poa.getDebug()) {
          ORBUtility.dprint(this,
              "internalGetServant: incarnate threw Throwable " + exc);
        }

        throw poa.invocationWrapper().poaServantActivatorLookupFailed(
            exc);
      } finally {
        poa.lock();

        // servant == null means incarnate threw an exception,
        // while servant instanceof NullServant means incarnate returned a
        // null servant.  Either case is an incarnate failure to the
        // entry state machine.
        if ((servant == null) || (servant instanceof NullServant)) {
          if (poa.getDebug()) {
            ORBUtility.dprint(this,
                "internalGetServant: incarnate failed");
          }

          // XXX Does the AOM leak in this case? Yes,
          // but the problem is hard to fix.  There may be
          // a number of threads waiting for the state to change
          // from INCARN to something else, which is VALID or
          // INVALID, depending on the incarnate result.
          // The activeObjectMap.get() call above creates an
          // ActiveObjectMap.Entry if one does not already exist,
          // and stores it in the keyToEntry map in the AOM.
          entry.incarnateFailure();
        } else {
          // here check for unique_id policy, and if the servant
          // is already registered for a different ID, then throw
          // OBJ_ADAPTER exception, else activate it. Section 11.3.5.1
          // 99-10-07.pdf
          if (isUnique) {
            // check if the servant already is associated with some id
            if (activeObjectMap.contains((Servant) servant)) {
              if (poa.getDebug()) {
                ORBUtility.dprint(this,
                    "internalGetServant: servant already assigned to ID");
              }

              entry.incarnateFailure();
              throw poa.invocationWrapper().poaServantNotUnique();
            }
          }

          if (poa.getDebug()) {
            ORBUtility.dprint(this,
                "internalGetServant: incarnate complete");
          }

          entry.incarnateComplete();
          activateServant(key, entry, (Servant) servant);
        }
      }

      return servant;
    } finally {
      if (poa.getDebug()) {
        ORBUtility.dprint(this,
            "Exiting POAPolicyMediatorImpl_R_USM.internalGetServant " +
                "for poa " + poa);
      }
    }
  }

  public void returnServant() {
    OAInvocationInfo info = orb.peekInvocationInfo();
    byte[] id = info.id();
    ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
    AOMEntry entry = activeObjectMap.get(key);
    entry.exit();
  }

  public void etherealizeAll() {
    if (activator != null) {
      Set keySet = activeObjectMap.keySet();

      // Copy the elements in the set to an array to avoid
      // changes in the set due to concurrent modification
      ActiveObjectMap.Key[] keys =
          (ActiveObjectMap.Key[]) keySet.toArray(
              new ActiveObjectMap.Key[keySet.size()]);

      for (int ctr = 0; ctr < keySet.size(); ctr++) {
        ActiveObjectMap.Key key = keys[ctr];
        AOMEntry entry = activeObjectMap.get(key);
        Servant servant = activeObjectMap.getServant(entry);
        if (servant != null) {
          boolean remainingActivations =
              activeObjectMap.hasMultipleIDs(entry);

          // Here we etherealize in the thread that called this
          // method, rather than etherealizing in a new thread
          // as in the deactivate case.  We still inform the
          // entry state machine so that only one thread at a
          // time can call the etherealize method.
          entry.startEtherealize(null);
          try {
            poa.unlock();
            try {
              activator.etherealize(key.id, poa, servant, true,
                  remainingActivations);
            } catch (Exception exc) {
              // ignore all exceptions
            }
          } finally {
            poa.lock();
            entry.etherealizeComplete();
          }
        }
      }
    }
  }

  public ServantManager getServantManager() throws WrongPolicy {
    return activator;
  }

  public void setServantManager(
      ServantManager servantManager) throws WrongPolicy {
    if (activator != null) {
      throw poa.invocationWrapper().servantManagerAlreadySet();
    }

    if (servantManager instanceof ServantActivator) {
      activator = (ServantActivator) servantManager;
    } else {
      throw poa.invocationWrapper().servantManagerBadType();
    }
  }

  public Servant getDefaultServant() throws NoServant, WrongPolicy {
    throw new WrongPolicy();
  }

  public void setDefaultServant(Servant servant) throws WrongPolicy {
    throw new WrongPolicy();
  }

  class Etherealizer extends Thread {

    private POAPolicyMediatorImpl_R_USM mediator;
    private ActiveObjectMap.Key key;
    private AOMEntry entry;
    private Servant servant;
    private boolean debug;

    public Etherealizer(POAPolicyMediatorImpl_R_USM mediator,
        ActiveObjectMap.Key key, AOMEntry entry, Servant servant,
        boolean debug) {
      this.mediator = mediator;
      this.key = key;
      this.entry = entry;
      this.servant = servant;
      this.debug = debug;
    }

    public void run() {
      if (debug) {
        ORBUtility.dprint(this, "Calling Etherealizer.run on key " +
            key);
      }

      try {
        try {
          mediator.activator.etherealize(key.id, mediator.poa, servant,
              false, mediator.activeObjectMap.hasMultipleIDs(entry));
        } catch (Exception exc) {
          // ignore all exceptions
        }

        try {
          mediator.poa.lock();

          entry.etherealizeComplete();
          mediator.activeObjectMap.remove(key);

          POAManagerImpl pm = (POAManagerImpl) mediator.poa.the_POAManager();
          POAFactory factory = pm.getFactory();
          factory.unregisterPOAForServant(mediator.poa, servant);
        } finally {
          mediator.poa.unlock();
        }
      } finally {
        if (debug) {
          ORBUtility.dprint(this, "Exiting Etherealizer.run");
        }
      }
    }
  }

  public void deactivateHelper(ActiveObjectMap.Key key, AOMEntry entry,
      Servant servant) throws ObjectNotActive, WrongPolicy {
    if (activator == null) {
      throw poa.invocationWrapper().poaNoServantManager();
    }

    Etherealizer eth = new Etherealizer(this, key, entry, servant, poa.getDebug());
    entry.startEtherealize(eth);
  }

  public Servant idToServant(byte[] id)
      throws WrongPolicy, ObjectNotActive {
    ActiveObjectMap.Key key = new ActiveObjectMap.Key(id);
    AOMEntry entry = activeObjectMap.get(key);

    Servant servant = activeObjectMap.getServant(entry);
    if (servant != null) {
      return servant;
    } else {
      throw new ObjectNotActive();
    }
  }
}
